
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <engine.h>
#include <vfs.h>
#include <sharedvalue.h>
#include <sharedvaluemanager.h>
#include <sstream>
#include <math/matrix.h>
#include <math/quaternion.h>
#include <SDL.h>
#include <GL/glew.h>
#include <gfx/light.h>
#include <gfx/material.h>
#include <gfx/mesh.h>
#include <gfx/image.h>
#include <gfx/itexture.h>
#include <gfx/iindexbuffer.h>
#include <gfx/ivertexbuffer.h>
#include <gfx/ivertexdeclaration.h>
#include <gfx/programparametername.h>
#include <gfx/projector.h>
#include <gfx/texturepack.h>
#include <gfx/vertex.h>
#include <gfx/generator/grassinstancegenerator.h>
#include <gfx/gl20/renderloop.h>
#include <ldr/loadingsystem.h>
#include <ldr/bin/ilimageloader.h>
#include <ldr/xml/xmldefaultresourcelocator.h>
#include <ldr/xml/xmlmaterialloader.h>
#include <ldr/xml/xmlmeshloader.h>
#include <ldr/xml/xmlnodeloader.h>
#include <ldr/xml/xmltextureloader.h>
#include <manager.h>
#include <physics/physicsworld.h>
#include <scene/billboardanimator.h>
#include <scene/entitynode.h>
#include <scene/impostereffectcamera.h>
#include <scene/geometry.h>
#include <scene/node.h>
#include <scene/light.h>
#include <scene/lodnode.h>
#include <scene/portal.h>
#include <scene/renderbucket.h>
#include <scene/scene.h>
#include <scene/sector.h>
#include <scene/skeleton.h>
#include <scene/skeletonbone.h>
#include <scene/sky.h>
#include <utils/lodgenerator.h>
#include <utils/meshcreator.h>
#include <utils/transformer.h>

#define FULLSCREEN  0

#if FULLSCREEN

#ifdef WIN32
// this is no limitation for windows but my windows development machine doesn't has more
// and those settings are not yet read from a settings file.
#define WINDOW_WIDTH 1680
#define WINDOW_HEIGHT 945
#else
#define WINDOW_WIDTH 1680
#define WINDOW_HEIGHT 1050
#endif

#else

#define WINDOW_WIDTH 1024
#define WINDOW_HEIGHT 768

#endif


#ifdef __WIN32__
#include <windows.h>

#ifdef LoadImage
#undef LoadImage
#define LoadImage LoadImage
#endif

#endif


ceEngine engine;
ceEntityNode* cameraNode;

ceEntityNode* moving_light_node;
ceProjector   projector;
cePortal*     portal = 0;
ceScene       scene;
ceMeshCreator mc;

const char*   material_name = 0;

ceRenderLoop*         renderLoop;
ceSharedValueFloat*		svTime;
ceSharedValueVector4*	svWind;
ceSharedValueVector3* svSunPos;

float effectEval = 0.0f;
float textureEval = 0.0f;
bool renderWireframe = false;
bool renderBoundingBoxes = false;

bool keyA = false;
bool keyD = false;
bool keyS = false;
bool keyW = false;
int  relX = 0;
int  relY = 0;
bool moveFast = false;

unsigned timeStart = 0;
unsigned timeValue = 0;

void debug_matrix (const ceMatrix4f& mat);

void clear_input ()
{
  relX = relY = 0;
}

bool handle_input (SDL_Event &e)
{
#define KEY(s,b,v) if (e.key.keysym.sym == s) b = v;
  switch (e.type)
    {
      case SDL_KEYDOWN:
        KEY(SDLK_a, keyA, true);
        KEY(SDLK_d, keyD, true);
        KEY(SDLK_s, keyS, true);
        KEY(SDLK_w, keyW, true);
        KEY(SDLK_LSHIFT, moveFast, true);
        break;
      case SDL_KEYUP:
        KEY(SDLK_a, keyA, false);
        KEY(SDLK_d, keyD, false);
        KEY(SDLK_s, keyS, false);
        KEY(SDLK_w, keyW, false);
        KEY(SDLK_LSHIFT, moveFast, false);
				if (e.key.keysym.sym == SDLK_u)
					{
						renderLoop->PerformUpdate (!renderLoop->IsPerformUpdate ());
					}
        if (e.key.keysym.sym == SDLK_i)
          {
            renderLoop->SetShadowEnabled(!renderLoop->IsShadowEnabled());
          }
				if (e.key.keysym.sym == SDLK_p && portal)
					{
						portal->SetOpen (!portal->IsOpen ());
					}
        break;
      case SDL_MOUSEMOTION:
        relX = e.motion.xrel;
        relY = -e.motion.yrel;
        break;
      }
#undef KEY
	return true;
}

void handle_camera ()
{
  static float rotH = 0.0f;
  static float rotV = 0.0f;

  float speed = moveFast ? 0.08f : 0.04f;

  ceVector3f dir;
  dir.x -= keyA ? speed : 0.0f;
  dir.x += keyD ? speed : 0.0f;
  dir.y -= keyS ? speed : 0.0f;
  dir.y += keyW ? speed : 0.0f;

  rotH -= (float)relX / 10.0f;
  rotV += (float)relY / 10.0f;

  ceVector3f origin = cameraNode->GetLocation();

  cameraNode->ClearTransformation();
  cameraNode->Rotate(ceVector3f(0, 0, 1), rotH);
  cameraNode->Rotate(ceVector3f(1, 0, 0), rotV);
  cameraNode->SetTranslation(origin);
  cameraNode->Translate(dir);
  cameraNode->FinishTransformation(false);
}

void read_matrix (unsigned type, ceMatrix4f& mat)
{
	glGetFloatv(type, (GLfloat*)(mat.m));
}

ceMatrix4f get_matrix (unsigned type)
{
  ceMatrix4f mat;
	read_matrix (type, mat);
	return mat;
}


iMesh* create_plane_mesh (iDevice* device, float x, float y, unsigned num_u, unsigned num_v)
{
	ceVertexElement vertexElements []  =
		{ ceVertexElement(VSD_Position0,	DT_Float, 3, 0,  56, 0),
			ceVertexElement(VSD_Normal0,		DT_Float, 3, 12, 56, 0),
			ceVertexElement(VSD_TexCoord0,	DT_Float, 2, 24, 56, 0),
			ceVertexElement(VSD_Tangent0,		DT_Float, 3, 32, 56, 0),
			ceVertexElement(VSD_Binormal0,	DT_Float, 3, 44, 56, 0),

			ceVertexElement()
		};

  unsigned num_vertices = (num_u+1) * (num_v+1);

  float *vertices = new float[num_vertices * 14];
  float *vptr = vertices;
  for (unsigned v=0; v<=num_v; ++v)
    {
      float fv = (float)v / (float)num_v;
      for (unsigned u=0; u<=num_u; ++u)
        {
          float fu = (float)u / (float)num_u;

          *vptr++ = -x + fu * 2.0f * x;
          *vptr++ = -y + fv * 2.0f * y;
          *vptr++ = 0.0f;

          *vptr++ = 0.0f;
          *vptr++ = 0.0f;
          *vptr++ = 1.0f;

          *vptr++ = fu;
          *vptr++ = fv;

          *vptr++ = 2.0f * x;
          *vptr++ = 0.0f;
          *vptr++ = 0.0f;

          *vptr++ = 0.0f;
          *vptr++ = 2.0f * y;
          *vptr++ = 0.0f;
        }
    }

  unsigned num_indices = num_u * num_v * 6;
  unsigned short *indices = new unsigned short[num_indices];
  unsigned short *iptr = indices;
  for (unsigned v=0; v<num_v; ++v)
    {
      for (unsigned u=0; u<num_u; ++u)
        {
          unsigned i00 = v * (num_u+1) + u;
          unsigned i01 = i00 + 1;

          unsigned i10 = i00 + num_u + 1;
          unsigned i11 = i10 + 1;

          *iptr++ = i00;
          *iptr++ = i10;
          *iptr++ = i11;

          *iptr++ = i00;
          *iptr++ = i11;
          *iptr++ = i01;
        }
    }


	iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);
  iVertexBuffer* vertexBuffer = device->CreateVertexBuffer(sizeof(float) * 14 * num_vertices, vertices, BDM_Static);
  iIndexBuffer* indexBuffer = device->CreateIndexBuffer(sizeof(unsigned short) * num_indices, indices, BDM_Static);

  // clean up... those buffers are not needed anymore
  delete [] vertices;
  delete [] indices;

	ceBoundingBox bbox;
	bbox.Clear();
	bbox.Add(ceVector3f (-x, -y, -1));
	bbox.Add(ceVector3f ( x,  y,  1));
	bbox.Update();

	ceMesh* mesh = new ceMesh();
	mesh->SetVertexDeclaration(vertDecl);
	mesh->SetVertices(vertexBuffer);
	mesh->SetIndices(indexBuffer);
	mesh->Set32BitIndices(false);
	mesh->SetBoundingBox(bbox);
  mesh->SetNumberOfTrigons(num_u * num_v * 2);

	iStaticMesh* staticMesh = device->CreateStaticMesh();
	staticMesh->SetMesh(mesh);

  return staticMesh;
}


void init_system ()
{
  ceLoadingSystem::Get ()->AddLoader (new ceILImageLoader());
  ceLoadingSystem::Get ()->AddLoader (new ceXMLMaterialLoader ());
  ceLoadingSystem::Get ()->AddLoader (new ceXMLTextureLoader ());
  ceLoadingSystem::Get ()->AddLoader (new ceXMLMeshLoader ());
  ceLoadingSystem::Get ()->AddLoader (new ceXMLNodeLoader ());
  ceLoadingSystem::Get ()->AddResourceLocator(new ceXMLDefaultResourceLocator ("resources.xml"));

  ceLinearLevelOfDetailSolver *default_effect_solver = new ceLinearLevelOfDetailSolver (0.3f, 0.0f, true);
  ceSolverManager::Get()->SetDefaultEffectSolver(default_effect_solver);

}

bool init (iDevice* device)
{
	svTime = new ceSharedValueFloat		("Time", 0.0f);
	svWind = new ceSharedValueVector4 ("Wind", ceVector4f (1.0f, 0.0f, 0.0f, 0.5f));
  svSunPos = new ceSharedValueVector3 ("SunPos", ceVector3f (0.0f, 0.0f, 0.0f));
	ceSharedValueManager::Get ()->AddValue (svTime);
	ceSharedValueManager::Get ()->AddValue (svWind);
  ceSharedValueManager::Get ()->AddValue (svSunPos);


	ceMaterialManager* mmgr = ceMaterialManager::Get ();
  ceMaterial* material = mmgr->GetMaterial (material_name);


  // Create the main sector for this app

  ceSector* sector0 = new ceSector ();


  // we just use a single plane to test our stuff
  iMesh *plane_mesh = create_plane_mesh(device, 50.0f, 50.0f, 10, 10);
  ceGeometry* plane_geometry = new ceGeometry (plane_mesh, material);
  ceEntityNode* plane_node = new ceEntityNode ();
  plane_node->SetGeometry (plane_geometry);
  plane_node->SetTranslation (ceVector3f (0, 0, 0));
  plane_node->SetName ("Terrain0");
  plane_node->FinishTransformation (false);




  ceLight* light = new ceLight();
  light->Ambient = ceColor4f (0.0f, 0.0f, 0.0f);
  light->Diffuse = ceColor4f (1.0f, 1.0f, 1.0f);
  light->Specular = ceColor4f (1.0f, 1.0f, 1.0f);
  light->Range = 1000.0f;
  light->RangeMode = LRM_Linear;


  ceLightObject* light_object = new ceLightObject(light);
  light_object->SetCastShadow(true);

  ceEntityNode* light_node = new ceEntityNode();
  light_node->SetLight(light_object);
  light_node->SetTranslation(ceVector3f (200.0f, 200.0f, 200.0f));
  light_node->FinishTransformation(false);

  svSunPos->SetValue(ceVector3f (200.0f, 200.0f, 200.0f).Normalized());

  moving_light_node = light_node;

  // add the stuff to the sector
  sector0->AddNode(plane_node);
  sector0->AddNode(light_node);


  // create the camera and setup the projector.
  ceCamera* camera = new ceCamera();
	camera->SetSector (sector0);
  cameraNode = new ceEntityNode();
  cameraNode->SetCamera(camera);
  cameraNode->ClearTransformation();
  cameraNode->SetTranslation(ceVector3f (0, 0, 0));
  cameraNode->LookAt(ceVector3f (0, 0, 0), ceVector3f (0, 0, 1));
  cameraNode->FinishTransformation(false);

  float a = 768.0f / 1024.0f;
  projector.SetLeft(-1.0f);
  projector.SetRight(1.0f);
  projector.SetBottom(-a);
  projector.SetTop(a);
  projector.SetNear(1.0f);
  projector.SetFar(1024.0f);
  projector.FinishTransformation();


  // finalize the scene
  scene.AddSector(sector0);
	scene.FindSectorLights ();
	scene.ApplyLights ();


  // and thats all
  return true;
}

void debug_matrix (const ceMatrix4f& mat)
{
  printf ("Matrix:\n");
	printf ("  %.2f %.2f %.2f %.2f\n", mat._00, mat._01, mat._02, mat._03);
	printf ("  %.2f %.2f %.2f %.2f\n", mat._10, mat._11, mat._12, mat._13);
	printf ("  %.2f %.2f %.2f %.2f\n", mat._20, mat._21, mat._22, mat._23);
  printf ("  %.2f %.2f %.2f %.2f\n\n", mat._30, mat._31, mat._32, mat._33);
  fflush (stdout);
}

void update_per_frame_data ()
{
  timeValue = SDL_GetTicks();

  unsigned timeDiff = timeValue - timeStart;

  float time = timeDiff / 1000.0f;
  svTime->SetValue (time);

  float windRot = time * 0.1f;
  svWind->SetValue (ceVector4f (cos(windRot), sin(windRot), 0.0f, 0.25f));


  float light_rot = time * 0.5f;
	ceVector3f light_pos = ceVector3f (sin (light_rot) * 10.0f, cos(light_rot) * 10.0f, 10.0f);
	light_pos = ceVector3f (0, 0, 20);
  svSunPos->SetValue(light_pos.Normalized());
  moving_light_node->SetTranslation(light_pos);
  moving_light_node->FinishTransformation(false);


}


void render (iDevice* device)
{

  if (renderWireframe)
    {
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }
  else
    {
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

  renderLoop->Render(device, &scene, *cameraNode->GetCamera(), projector);
}


int main (int argc, char** argv)
{
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);

  timeStart = SDL_GetTicks();

  ceEngine::Config config;
  memset(&config, 0, sizeof(ceEngine::Config));
	config.windowCreate = true;
  config.windowWidth = WINDOW_WIDTH;
  config.windowHeight = WINDOW_HEIGHT;
  config.windowFullscreen = FULLSCREEN;

	if (!engine.ReadCommandLineParams (argc, argv))
		{
			printf ("False Pathname\n");
			return -1;
		}

  for (unsigned i=0; i<argc; ++i)
    {
      if (!argv[i])
        break;

      if (strcmp(argv[i], "--material") == 0 && (i+1) < argc)
        {
          i++;
          material_name = argv[i++];
        }
    }

  if (!material_name)
    {
      printf ("Please specify a material name\n");
      fflush(stdout);
      return -1;
    }


	if (!engine.Initialize(config))
	  {
	    printf ("Unable to configure engine\n");
      return -1;
	  }


  iDevice* device = engine.GetDevice();
  if (!mc.Initialize (device))
		{
			return -1;
		}

  renderLoop = new ceRenderLoop();
  if (!renderLoop->Initialize(device))
    {
      return -1;
    }
  renderLoop->SetShadowMapDistances(5, 10, 20);
  renderLoop->SetViewport (device, engine.GetWindow());
	renderLoop->SetShadowEnabled (false);

  init_system();
	if (!init (device))
    {
      printf ("Unable to initialize the data\n");
      return -1;
		}
	printf ("Initialised\n");


  SDL_WM_GrabInput(SDL_GRAB_ON);
  SDL_ShowCursor(0);

	bool running = true;
  unsigned frames = 0;

  unsigned lastTime = SDL_GetTicks();
	while (running)
		{
			SDL_Event e;
      clear_input();
			while (SDL_PollEvent(&e))
				{
			    running = handle_input(e);
					switch (e.type)
						{
							case SDL_KEYUP:
                if (e.key.keysym.sym == SDLK_r)
                  {
                    renderWireframe = !renderWireframe;
                  }
                if (e.key.keysym.sym == SDLK_b)
                  {
                    renderBoundingBoxes = !renderBoundingBoxes;
                  }
                if (e.key.keysym.sym == SDLK_ESCAPE)
                  {
                    running = false;
                  }
                break;
						}
				}
      handle_camera();
      frames++;
      unsigned currentTime = SDL_GetTicks();
      if (currentTime - lastTime >= 1000)
        {
          char str[256];
          sprintf (str, "VisionGL: FPS: %d", frames);
          frames = 0;
//          printf ("%s\n", str);
//          fflush(stdout);
          lastTime = currentTime;
          SDL_WM_SetCaption(str, 0);
        }
		
      update_per_frame_data ();
      render (device);
      SDL_GL_SwapBuffers();
		}
  SDL_WM_GrabInput(SDL_GRAB_OFF);
  SDL_ShowCursor(1);

  return 0;
}
